/* * Copyright 2015 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.apiman.manager.api.war; import io.apiman.common.logging.IApimanDelegateLogger; import io.apiman.common.logging.IApimanLogger; import io.apiman.common.plugin.Plugin; import io.apiman.common.plugin.PluginClassLoader; import io.apiman.common.plugin.PluginCoordinates; import io.apiman.common.util.ReflectionUtils; import io.apiman.common.util.crypt.CurrentDataEncrypter; import io.apiman.common.util.crypt.IDataEncrypter; import io.apiman.manager.api.beans.idm.UserBean; import io.apiman.manager.api.core.IApiCatalog; import io.apiman.manager.api.core.IApiKeyGenerator; import io.apiman.manager.api.core.IMetricsAccessor; import io.apiman.manager.api.core.INewUserBootstrapper; import io.apiman.manager.api.core.IPluginRegistry; import io.apiman.manager.api.core.IStorage; import io.apiman.manager.api.core.IStorageQuery; import io.apiman.manager.api.core.UuidApiKeyGenerator; import io.apiman.manager.api.core.config.ApiManagerConfig; import io.apiman.manager.api.core.crypt.DefaultDataEncrypter; import io.apiman.manager.api.core.exceptions.StorageException; import io.apiman.manager.api.core.i18n.Messages; import io.apiman.manager.api.core.logging.ApimanLogger; import io.apiman.manager.api.core.logging.JsonLoggerImpl; import io.apiman.manager.api.core.logging.StandardLoggerImpl; import io.apiman.manager.api.core.noop.NoOpMetricsAccessor; import io.apiman.manager.api.es.DefaultEsClientFactory; import io.apiman.manager.api.es.ESMetricsAccessor; import io.apiman.manager.api.es.EsStorage; import io.apiman.manager.api.es.IEsClientFactory; import io.apiman.manager.api.jpa.JpaStorage; import io.apiman.manager.api.jpa.JpaStorageInitializer; import io.apiman.manager.api.security.ISecurityContext; import io.apiman.manager.api.security.impl.DefaultSecurityContext; import io.apiman.manager.api.security.impl.KeycloakSecurityContext; import io.searchbox.client.JestClient; import java.lang.reflect.Constructor; import java.util.Map; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.New; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.InjectionPoint; import javax.inject.Named; import org.apache.commons.lang3.StringUtils; /** * Attempt to create producer methods for CDI beans. * * @author eric.wittmann@redhat.com */ @ApplicationScoped public class WarCdiFactory { private static IEsClientFactory sStorageESClientFactory; private static IEsClientFactory sMetricsESClientFactory; private static JpaStorage sJpaStorage; private static EsStorage sESStorage; @Produces @ApimanLogger public static IApimanLogger provideLogger(WarApiManagerConfig config, InjectionPoint injectionPoint) { try { ApimanLogger logger = injectionPoint.getAnnotated().getAnnotation(ApimanLogger.class); Class<?> klazz = logger.value(); return getDelegate(config).newInstance().createLogger(klazz); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(String.format( Messages.i18n.format("LoggerFactory.InstantiationFailed")), e); //$NON-NLS-1$ } } @Produces @ApplicationScoped public static INewUserBootstrapper provideNewUserBootstrapper(WarApiManagerConfig config, IPluginRegistry pluginRegistry) { String type = config.getNewUserBootstrapperType(); if (type == null) { return new INewUserBootstrapper() { @Override public void bootstrapUser(UserBean user, IStorage storage) throws StorageException { // Do nothing special. } }; } else { try { return createCustomComponent(INewUserBootstrapper.class, config.getNewUserBootstrapperType(), config.getNewUserBootstrapperProperties(), pluginRegistry); } catch (Throwable t) { throw new RuntimeException("Error or unknown user bootstrapper type: " + config.getNewUserBootstrapperType(), t); //$NON-NLS-1$ } } } @Produces @ApplicationScoped public static ISecurityContext provideSecurityContext(WarApiManagerConfig config, @New DefaultSecurityContext defaultSC, @New KeycloakSecurityContext keycloakSC) { if ("default".equals(config.getSecurityContextType())) { //$NON-NLS-1$ return defaultSC; } else if ("keycloak".equals(config.getSecurityContextType())) { //$NON-NLS-1$ return keycloakSC; } else { throw new RuntimeException("Unknown security context type: " + config.getSecurityContextType()); //$NON-NLS-1$ } } @Produces @ApplicationScoped public static IStorage provideStorage(WarApiManagerConfig config, @New JpaStorage jpaStorage, @New EsStorage esStorage, IPluginRegistry pluginRegistry) { IStorage storage; if ("jpa".equals(config.getStorageType())) { //$NON-NLS-1$ storage = initJpaStorage(config, jpaStorage); } else if ("es".equals(config.getStorageType())) { //$NON-NLS-1$ storage = initEsStorage(config, esStorage); } else { try { storage = createCustomComponent(IStorage.class, config.getStorageType(), config.getStorageProperties(), pluginRegistry); } catch (Throwable t) { throw new RuntimeException("Error or unknown storage type: " + config.getStorageType(), t); //$NON-NLS-1$ } } return storage; } @Produces @ApplicationScoped public static IStorageQuery provideStorageQuery(WarApiManagerConfig config, @New JpaStorage jpaStorage, @New EsStorage esStorage, IStorage storage, IPluginRegistry pluginRegistry) { if ("jpa".equals(config.getStorageType())) { //$NON-NLS-1$ return initJpaStorage(config, jpaStorage); } else if ("es".equals(config.getStorageType())) { //$NON-NLS-1$ return initEsStorage(config, esStorage); } else if (storage != null && storage instanceof IStorageQuery) { return (IStorageQuery) storage; } else { try { return createCustomComponent(IStorageQuery.class, config.getStorageQueryType(), config.getStorageQueryProperties(), pluginRegistry); } catch (Throwable t) { throw new RuntimeException("Error or unknown storage query type: " + config.getStorageType(), t); //$NON-NLS-1$ } } } @Produces @ApplicationScoped public static IMetricsAccessor provideMetricsAccessor(WarApiManagerConfig config, @New NoOpMetricsAccessor noopMetrics, @New ESMetricsAccessor esMetrics, IPluginRegistry pluginRegistry) { IMetricsAccessor metrics; if ("es".equals(config.getMetricsType())) { //$NON-NLS-1$ metrics = esMetrics; } else if (config.getMetricsType().equals(ESMetricsAccessor.class.getName())) { metrics = esMetrics; } else if ("noop".equals(config.getMetricsType())) { //$NON-NLS-1$ metrics = noopMetrics; } else if (config.getMetricsType().equals(NoOpMetricsAccessor.class.getName())) { metrics = noopMetrics; } else { try { metrics = createCustomComponent(IMetricsAccessor.class, config.getMetricsType(), config.getMetricsProperties(), pluginRegistry); } catch (Throwable t) { System.err.println("Unknown apiman metrics accessor type: " + config.getMetricsType()); //$NON-NLS-1$ metrics = noopMetrics; } } return metrics; } @Produces @ApplicationScoped public static IApiKeyGenerator provideApiKeyGenerator(WarApiManagerConfig config, @New UuidApiKeyGenerator uuidApiKeyGen, IPluginRegistry pluginRegistry) { IApiKeyGenerator apiKeyGenerator; String type = config.getApiKeyGeneratorType(); if ("uuid".equals(type)) { //$NON-NLS-1$ apiKeyGenerator = uuidApiKeyGen; } else { try { apiKeyGenerator = createCustomComponent(IApiKeyGenerator.class, type, config.getApiKeyGeneratorProperties(), pluginRegistry); } catch (Exception e) { System.err.println("Unknown apiman API key generator type: " + type); //$NON-NLS-1$ System.err.println("Automatically falling back to UUID style API Keys."); //$NON-NLS-1$ apiKeyGenerator = uuidApiKeyGen; } } return apiKeyGenerator; } @Produces @ApplicationScoped public static IDataEncrypter provideDataEncrypter(@New DefaultDataEncrypter defaultEncrypter, WarApiManagerConfig config, IPluginRegistry pluginRegistry) { try { IDataEncrypter encrypter = createCustomComponent(IDataEncrypter.class, config.getDataEncrypterType(), config.getDataEncrypterProperties(), pluginRegistry, defaultEncrypter); CurrentDataEncrypter.instance = encrypter; return encrypter; } catch (Throwable t) { throw new RuntimeException("Error or unknown data encrypter type: " + config.getDataEncrypterType(), t); //$NON-NLS-1$ } } @Produces @ApplicationScoped public static IApiCatalog provideApiCatalog(WarApiManagerConfig config, IPluginRegistry pluginRegistry) { try { return createCustomComponent(IApiCatalog.class, config.getApiCatalogType(), config.getApiCatalogProperties(), pluginRegistry); } catch (Throwable t) { throw new RuntimeException("Error or unknown API catalog type: " + config.getApiCatalogType(), t); //$NON-NLS-1$ } } @Produces @ApplicationScoped @Named("storage-factory") public static IEsClientFactory provideStorageESClientFactory(WarApiManagerConfig config, IPluginRegistry pluginRegistry) { if ("es".equals(config.getStorageType()) && sStorageESClientFactory == null) { //$NON-NLS-1$ try { String factoryClass = config.getStorageESClientFactory(); if (factoryClass == null) { factoryClass = DefaultEsClientFactory.class.getName(); } sStorageESClientFactory = createCustomComponent(IEsClientFactory.class, factoryClass, config.getStorageESClientFactoryConfig(), pluginRegistry); } catch (Exception e) { throw new RuntimeException(e); } } return sStorageESClientFactory; } @Produces @ApplicationScoped @Named("metrics-factory") public static IEsClientFactory provideMetricsESClientFactory(WarApiManagerConfig config, IPluginRegistry pluginRegistry) { if ("es".equals(config.getMetricsType()) && sMetricsESClientFactory == null) { //$NON-NLS-1$ try { String factoryClass = config.getMetricsESClientFactory(); if (factoryClass == null) { factoryClass = DefaultEsClientFactory.class.getName(); } sMetricsESClientFactory = createCustomComponent(IEsClientFactory.class, factoryClass, config.getMetricsESClientFactoryConfig(), pluginRegistry); } catch (Exception e) { throw new RuntimeException(e); } } return sMetricsESClientFactory; } @Produces @ApplicationScoped @Named("storage") public static JestClient provideStorageESClient(WarApiManagerConfig config, @Named("storage-factory") IEsClientFactory clientFactory) { if ("es".equals(config.getStorageType())) { //$NON-NLS-1$ return clientFactory.createClient(); } else { return null; } } @Produces @ApplicationScoped @Named("metrics") public static JestClient provideMetricsESClient(WarApiManagerConfig config, @Named("metrics-factory") IEsClientFactory clientFactory) { if ("es".equals(config.getMetricsType())) { //$NON-NLS-1$ return clientFactory.createClient(); } else { return null; } } /** * Initializes the ES storage (if required). * @param config * @param esStorage */ private static EsStorage initEsStorage(WarApiManagerConfig config, EsStorage esStorage) { if (sESStorage == null) { sESStorage = esStorage; sESStorage.setIndexName(config.getStorageESIndexName()); if (config.isInitializeStorageES()) { sESStorage.initialize(); } } return sESStorage; } /** * Initializes the JPA storage (if required). This basically amounts to installing * the DDL in the database. This is optional and disabled by default. * @param config * @param jpaStorage */ private static JpaStorage initJpaStorage(ApiManagerConfig config, JpaStorage jpaStorage) { if (sJpaStorage == null) { sJpaStorage = jpaStorage; if (config.isInitializeStorageJPA()) { JpaStorageInitializer initializer = new JpaStorageInitializer(config.getHibernateDataSource(), config.getHibernateDialect()); initializer.initialize(); } } return sJpaStorage; } /** * Creates a custom component from information found in the properties file. * @param componentType * @param componentSpec * @param configProperties * @param pluginRegistry * @throws Exception */ private static <T> T createCustomComponent(Class<T> componentType, String componentSpec, Map<String, String> configProperties, IPluginRegistry pluginRegistry) throws Exception { return createCustomComponent(componentType, componentSpec, configProperties, pluginRegistry, null); } /** * Creates a custom component from information found in the properties file. * @param componentType * @param componentSpec * @param configProperties * @param pluginRegistry */ private static <T> T createCustomComponent(Class<T> componentType, String componentSpec, Map<String, String> configProperties, IPluginRegistry pluginRegistry, T defaultComponent) throws Exception { if (componentSpec == null && defaultComponent == null) { throw new IllegalArgumentException("Null component type."); //$NON-NLS-1$ } if (componentSpec == null && defaultComponent != null) { return defaultComponent; } if (componentSpec.startsWith("class:")) { //$NON-NLS-1$ Class<?> c = ReflectionUtils.loadClass(componentSpec.substring("class:".length())); //$NON-NLS-1$ return createCustomComponent(componentType, c, configProperties); } else if (componentSpec.startsWith("plugin:")) { //$NON-NLS-1$ PluginCoordinates coordinates = PluginCoordinates.fromPolicySpec(componentSpec); if (coordinates == null) { throw new IllegalArgumentException("Invalid plugin component spec: " + componentSpec); //$NON-NLS-1$ } int ssidx = componentSpec.indexOf('/'); if (ssidx == -1) { throw new IllegalArgumentException("Invalid plugin component spec: " + componentSpec); //$NON-NLS-1$ } String classname = componentSpec.substring(ssidx + 1); Plugin plugin = pluginRegistry.loadPlugin(coordinates); PluginClassLoader classLoader = plugin.getLoader(); Class<?> class1 = classLoader.loadClass(classname); return createCustomComponent(componentType, class1, configProperties); } else { Class<?> c = ReflectionUtils.loadClass(componentSpec); return createCustomComponent(componentType, c, configProperties); } } /** * Creates a custom component from a loaded class. * @param componentType * @param componentClass * @param configProperties */ @SuppressWarnings("unchecked") private static <T> T createCustomComponent(Class<T> componentType, Class<?> componentClass, Map<String, String> configProperties) throws Exception { if (componentClass == null) { throw new IllegalArgumentException("Invalid component spec (class not found)."); //$NON-NLS-1$ } try { Constructor<?> constructor = componentClass.getConstructor(Map.class); return (T) constructor.newInstance(configProperties); } catch (Exception e) { } return (T) componentClass.getConstructor().newInstance(); } private static Class<? extends IApimanDelegateLogger> getDelegate(WarApiManagerConfig config) { if(config.getLoggerName() == null || StringUtils.isEmpty(config.getLoggerName())) { System.err.println(Messages.i18n.format("LoggerFactory.NoLoggerSpecified")); //$NON-NLS-1$ return StandardLoggerImpl.class; } switch(config.getLoggerName().toLowerCase()) { case "json": //$NON-NLS-1$ return JsonLoggerImpl.class; case "standard": //$NON-NLS-1$ return StandardLoggerImpl.class; default: return loadByFQDN(config.getLoggerName()); } } @SuppressWarnings("unchecked") private static Class<? extends IApimanDelegateLogger> loadByFQDN(String fqdn) { try { return (Class<? extends IApimanDelegateLogger>) Class.forName(fqdn); } catch (ClassNotFoundException e) { System.err.println(String.format(Messages.i18n.format("LoggerFactory.LoggerNotFoundOnClasspath"), //$NON-NLS-1$ fqdn)); return StandardLoggerImpl.class; } } }